package server;
import gestion_bd.ConectorBD;
import gestion_bd.FingerprintCalculator;

import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Map;

import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;

import common.Mensaje;

/**
 * Clase principal del servidor. Es quien mantiene el estado actual en el que se encuentra el servidor y puede acceder a los distintos módulos
 * que lo componen. Se mantiene en ejecución en el thread principal y crea los restantes hilos que interactuan con los demás componentes del
 * sistema. Se encarga de efecutar los controles que sean necsarios para mantener la consistencia entre los distintos dispositivos conectados 
 * a él. Además cuenta con acceso a la base de datos para registrar o consultar información.
 * @author Matias
 *
 */
public class ServidorPrincipal {

	private int puertoClientes;
	private int puertoAdmin;
	private int puertoConsultas;

	private static final int TIME_OUT_VOTACION =90000;// time out para maximo tiempo de votación
	
	
	private int estado_actual;

	private SSLServerSocket socket_servidor_android;
	private SSLServerSocket socket_servidor_admin;

	private SSLSocket conexion_admin;

	private boolean fin_de_session= false;

	private boolean admitiendo_clientes=true;

	private Map<String, ThreadServerAndroid> mapeoClientesAndroid;
	private LinkedList<ThreadServerAndroid> listaClientesAndroid;
	private FingerprintCalculator finger;
	private ConectorBD conectorbd;

	private ThreadServerAdmin thread_admin;

	private ServiciosSSL serviciosSSL;

	private String temaActual;
	private int idSesionActual;
	private int idTemaActual;

	protected boolean hayRtdos=false;

	private static final int POSITIVOS = 0;
	private static final int NEGATIVOS = 1;
	private static final int ABSTENCION = 2;

	private boolean e5_pasar_sig_estado;
	private boolean e4_pasar_sig_estado;

	private int quorum;

	private Log log;

	/**
	 * Constructor 1 de la clase [ESTE NO ES EL QUE SE UTILIZA!!].
	 * @param ptoConsultas : entero con el puerto donde se recibiran las consultas a la base de datos.
	 * @param ptoAdmin : entero con el puerto donde se escuchará al cliente admin.
	 * @param ptoClientes : : entero con el puerto donde se recibiran los clientes android
	 */
	public ServidorPrincipal(int ptoConsultas, int ptoAdmin, int ptoClientes){

		log = new Log();

		puertoConsultas = ptoConsultas;
		puertoAdmin = ptoAdmin;
		puertoClientes = ptoClientes;

		estado_actual=0;
		fin_de_session=false;
		conectorbd = ConectorBD.getInstancia();

		try {
			conectorbd.conectar("root", "");

			mapeoClientesAndroid = new Hashtable<String, ThreadServerAndroid>(50);
			listaClientesAndroid= new LinkedList<ThreadServerAndroid>();
			finger = new FingerprintCalculator();
			temaActual=null;
			serviciosSSL = new ServiciosSSL(log);
			serviciosSSL.iniciar_keystores("123456".toCharArray(), "abcdefg".toCharArray()); // (a no asustarse, este constructor es solo para testeo)
			iniciar_servidor();
		} catch (SQLException e) {

			//System.err.println("No se pudo efectuar la conexi�n a la Base de datos.\nAsegurese de que el servidro MySQL este corriendo\nDetalles:"+e.getMessage());
			String msj="No se pudo efectuar la conexi�n a la Base de datos.\nAsegurese de que el servidro MySQL este corriendo\nDetalles:"+e.getMessage();
			log.writeLog(Log.WARNING, "SERVIDOR", "Conexion BD", msj);
		}
	}

	//segundo constructor recibiendo el valor del quorum
	/**
	 * Constructor 2 de la clase.
	 * @param ptoConsultas : entero con el puerto donde se recibiran las consultas a la base de datos.
	 * @param ptoAdmin : entero con el puerto donde se escuchará al cliente admin.
	 * @param ptoClientes : : entero con el puerto donde se recibiran los clientes android
	 * @param q : entero con el nro necesario para el quorum.
	 * @param pass : cadena de carácteres con el password del jks [es el mismo que el de la clave]
	 */
	public ServidorPrincipal(int ptoConsultas, int ptoAdmin, int ptoClientes, int q, char[] pass){
		System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nSistema de Voto Electrónico para la Asamblea Universitaria de la UNS\nVersión 1.1\nCarrasco,Liberman 2013-2014\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
		log = new Log();
		
		
		quorum=q;
		puertoConsultas = ptoConsultas;
		puertoAdmin = ptoAdmin;
		puertoClientes = ptoClientes;

		estado_actual=0;
		fin_de_session=false;
		conectorbd = ConectorBD.getInstancia();

		try {
			conectorbd.conectar("root", "");// [$PELIGRO conecta como root a la bd]

			// creacón de estructuras auxiliares
			mapeoClientesAndroid = new Hashtable<String, ThreadServerAndroid>(50);
			listaClientesAndroid= new LinkedList<ThreadServerAndroid>();
			finger = new FingerprintCalculator();
			temaActual=null;
			serviciosSSL = new ServiciosSSL(log);

			// inicio del servidor en sí
			char[] passKey = (pass.length<7)? pass: Arrays.copyOfRange(pass, 0, 7);
			serviciosSSL.iniciar_keystores(pass, passKey);
			iniciar_servidor();

		} catch (SQLException e) {
			String msj = "No se pudo efectuar la conexión a la Base de datos.\nAsegurese de que el servidro MySQL este corriendo\nDetalles:"+e.getMessage();
			log.writeLog(Log.WARNING, "SERVIDOR", "Conexión BD", msj);
		}
	}

	/**
	 * Método con el bucle principal del servidor.
	 */
	public void iniciar_servidor(){
		while(!fin_de_session){
			switch (estado_actual){
			case 0:{ estado0(); break; }
			case 1:{ estado1(); break; }
			case 2:{ estado2(); break; }
			case 3:{ estado3(); break; }
			case 4:{ estado4(); break; }
			case 5:{ estado5(); break; }
			case 6:{ estado6(); break; }
			case 7:{ estado7(); break; }
			case 8:{ estado8(); break; }
			}

		}
	}
	/**
	 * Estado en el cual se crean los threads que atenderan a los demás componentes del sistema:
	 * - Thread para las consultas del admin
	 * - Thread para la conexión de control del admin
	 * - Thread que espera por conexiones de los clientes android (a su vez este genera nuevo thread por cada cliente nuevo) 
	 */
	private void estado0(){
		final ServidorPrincipal server_ppal = this;
		log.writeLog(Log.INFO, "SERVIDOR", "INICIO", "Servidor comienza su ejecución");

		// creación y ejecución del thread utilizado para las consultas por el admin.
		Runnable runnable_consulta = new Runnable() {
			public void run() {
				boolean flag_conecto_cliente = false;
				SSLSocket socket_consulta=null;
				SSLServerSocket socket_servidor_consulta = serviciosSSL.generarServidor(puertoConsultas);
				do{
					try {
						socket_consulta = (SSLSocket) socket_servidor_consulta.accept();
						serviciosSSL.printSocketInfo(socket_consulta);
						ThreadConsultas thread_consultas = new ThreadConsultas(socket_consulta, log);
						flag_conecto_cliente=true;
						thread_consultas.atender_consultas();
	
					} catch (SSLPeerUnverifiedException e) {
						String msj = "No pudo verificarse la identidad del cliente admin: "+e.getMessage();
						log.writeLog(Log.WARNING, "SERVIDOR", "Estado 0", msj);
						try {
							socket_consulta.close();
							//conexion_admin.close();
							//socket_servidor_admin.close();
						} catch (IOException e1) {
							System.err.println(e1.getMessage());
						}
					} catch (IOException e) {
						System.err.println(e.getMessage());
					}
				}while(!flag_conecto_cliente);

			}
		};
		Thread thread_consulta = new Thread(runnable_consulta);
		thread_consulta.start();


		// creación y ejecución del thread que atiende al cliente admin.
		Runnable runnable = new Runnable() {
			public void run() {
				socket_servidor_admin = serviciosSSL.generarServidor(puertoAdmin);
				// se utiliza este flag en el caso que se intente una conexión de un cliente inválido de manera
				// de no cerrar el socket y permanecer escuchando por el cliente válido.
				boolean flag_conecto_cliente=false; 
				do{
					try {
						conexion_admin = (SSLSocket) socket_servidor_admin.accept();
						serviciosSSL.printSocketInfo(conexion_admin);

						thread_admin = new ThreadServerAdmin(conexion_admin, server_ppal, log);
						flag_conecto_cliente = true;
						thread_admin.atender_admin();

					} catch (SSLPeerUnverifiedException e) {
						String msj = "No pudo verificarse la identidad del cliente admin: "+e.getMessage();
						log.writeLog(Log.WARNING, "SERVIDOR", "Conexión cliente Admin", msj);
						try {
							conexion_admin.close();
							//socket_servidor_admin.close();
						} catch (IOException e1) { 
							System.err.println(e.getMessage());
						}
					} catch (IOException e) {
						System.err.println(e.getMessage());
					}
				}while(!flag_conecto_cliente);

			}
		};
		Thread thread1 = new Thread(runnable);
		thread1.setPriority(Thread.MAX_PRIORITY);
		thread1.start();

		// iniciar el thread que acepta conexiones para los clientes android
		Runnable runnable2 = new Runnable() {
			public void run() {
				socket_servidor_android= serviciosSSL.generarServidor(puertoClientes);
				
				while(admitiendo_clientes){// aceptando clientes
					try{
						SSLSocket conexion_entrante = (SSLSocket) socket_servidor_android.accept();
						serviciosSSL.printSocketInfo(conexion_entrante);

						String fingerprint = finger.obtenerFingerprint(conexion_entrante.getSession().getPeerCertificates()[0]);

						if(cliente_habilitado_a_conectarse(fingerprint)){ // verifico que no haya otro cliente conectado con el mismo fingerprint y que est� habilitado a conectarse

							// creación del thread que atenderá a ese cliente en particular
							ThreadServerAndroid nuevo_cliente = new ThreadServerAndroid(conexion_entrante, server_ppal, fingerprint, log);
							log.writeLog(Log.INFO, "SERVIDOR", "Conexión Cliente Android", "Cliente Android Conectado Fingerprint: "+fingerprint);
							synchronized (mapeoClientesAndroid) {
								mapeoClientesAndroid.put(fingerprint, nuevo_cliente);
							}
							synchronized (listaClientesAndroid) {
								listaClientesAndroid.add(nuevo_cliente);
							}
							nuevo_cliente.start();

						}else{ // fingerprint duplicado
							String msj = "no se permitió la conexión al cliente "+fingerprint;
							log.writeLog(Log.WARNING, "SERVIDOR", "Conexión cliente Android", msj);
							conexion_entrante.close();
						}


					}catch(Exception e){
						String msj = "Se cerró serverSocket que atendia clientes android : "+e.getMessage();
						log.writeLog(Log.INFO, "SERVIDOR", "Conexión cliente Android", msj);
					}
				}//end-while
			}
		};
		Thread mythread2 = new Thread(runnable2);
		mythread2.start();

		estado_actual=1;
	}
	/**
	 * En este estado se espera que el cliente admini inicie la sesión
	 */
	private void estado1(){// este está bien
		// se está esperando por inicio de sesión por parte del admin
	}

	private void estado2(){
		// inició la sesión
		if(admitiendo_clientes){ // i.e. si ya deshabilitó las nuevas conexiones entrantes 
			// dejo el thread en espera hasta que admin los despierte (cuando envíe msj para dejar de admitir clientes)
			synchronized(this){
				try {
					this.wait();
				} catch (InterruptedException e) {System.out.println(e.getMessage());}
			}
			// establezco el valor del flag para que no ejecute esta porción de código otra vez
			admitiendo_clientes = false;
			try {
				socket_servidor_android.close();
			} catch (IOException e1) {System.err.println(e1.getMessage());}

			// se registran todos los clientes conectados en la BD
			for(ThreadServerAndroid tsa: mapeoClientesAndroid.values()){
				registrarClienteConectado(tsa.obtenerFingerprint());
			}

			log.writeLog(Log.INFO, "SERVIDOR", "ESTADO 2", "Se dejará de admitir nuevas conexiones");
		}
	}

	private void estado3(){
		if(verificarTodosNotificados()){
			synchronized(thread_admin){
				thread_admin.notify();
			}

		}
	}

	@SuppressWarnings("deprecation")
	private void estado4(){
		String msj = "Hay tema: "+temaActual;
		log.writeLog(Log.INFO, "SERVIDOR", "Estado 4", msj);

		hayRtdos=false;

		// por precaucion se crea un thread con un timeout para que luego de un cierto tiempo despierte al thread de ppal
		e4_pasar_sig_estado = false;
		final ServidorPrincipal server = this;
		// thread que lleva la cuenta del timeout (esto nos salvo durante la demo!!!)
		Runnable timeout_run = new Runnable(){
			public void run(){
				try {
					// sleep time out
					Thread.sleep(30000); // ###### ponerlo como constante!!!!!

					synchronized (server) {
						// cambiar condicion
						e4_pasar_sig_estado=true;  // habrá problema con atomicidad? (no, justamente est� dentro de un "synchronized". salame!)

						// despierto al thread del server
						server.notify();
					}
					log.writeLog(Log.WARNING, "SERVIDOR", "Estado 4", "Se pasa a siguiente estado por el TIMER");
				} catch (InterruptedException e) {
					System.err.println(e.getMessage());
				}
			}
		};
		Thread timeout_thread = new Thread(timeout_run);
		timeout_thread.start();

		e4_pasar_sig_estado=verificarTodosNotificadosE4();

		synchronized(this){
			while(!e4_pasar_sig_estado){
				try {

					this.wait(); // bloqueado hasta que recibe un voto de un cliente o timeout_thread lo despierta

					if(!e4_pasar_sig_estado){ // luego del wait la condicion podria cambiar

						e4_pasar_sig_estado= verificarTodosNotificadosE4();
					}

				} catch (InterruptedException e) {
					System.err.println(e.getMessage());
				}
			}
		}

		// si el thread del timeout aún está corriendo, lo detengo
		if(timeout_thread.isAlive())
			timeout_thread.stop(); 

		// verificar si hay quorum para ver si pasa a votación [OBS: el admin no debería de enviar msj de "INICIO VOTACION"???] 
		if(hayQuorum()){
			log.writeLog(Log.INFO, "SERVIDOR", "Estado 4", "Cambia a estado 5");
			estado_actual=5;
		}else{
			log.writeLog(Log.INFO, "SERVIDOR", "Estado 4", "Cambia a estado 7");
			estado_actual=7;
		}

	}

	@SuppressWarnings("deprecation")
	private void estado5(){
		e5_pasar_sig_estado = false; // cambiarlo

		final ServidorPrincipal server = this;
		// thread que lleva la cuenta del timeout
		Runnable timeout_run = new Runnable(){
			public void run(){
				try {
					// sleep time out
					Thread.sleep(TIME_OUT_VOTACION);

					synchronized (server) {
						// cambiar condicion
						e5_pasar_sig_estado=true;

						// despierto al thread del server
						server.notify();
					}
					log.writeLog(Log.WARNING, "SERVIDOR", "Estado 5", "Se pasa a siguiente estado por el TIMER");

				} catch (InterruptedException e) {
					System.err.println(e.getMessage());
				}
			}
		};
		Thread timeout_thread = new Thread(timeout_run);
		timeout_thread.start();

		synchronized(this){
			while(!e5_pasar_sig_estado){
				try {

					this.wait(); // bloqueado hasta que recibe un voto de un cliente o timeout_thread lo despierta
					if(!e5_pasar_sig_estado){ // luego del wait la condicion podria cambiar
						e5_pasar_sig_estado= verificarTodosVotaron1();

					}

				} catch (InterruptedException e) {
					System.err.println(e.getMessage());
				}
			}
		}

		// si el thread del timeout aún está corriendo, lo detengo
		if(timeout_thread.isAlive())
			timeout_thread.stop(); 

		// cambio al prox estado
		estado_actual=6;
		log.writeLog(Log.INFO, "SERVIDOR", "Estado 5", "pasa a estado 6");
	}



	private void estado6(){
		// procesando los rtdos de la votación
		if(!hayRtdos){
			log.writeLog(Log.INFO, "SERVIDOR", "ESTADO 6", "Procesando resultados");


			int[] rtdos = procesarRtdos();
			String rtdo = "Positivos: "+rtdos[POSITIVOS]+", Negativos: "+rtdos[NEGATIVOS]+", Abstenciones: "+rtdos[ABSTENCION]+".";
			log.writeLog(Log.INFO, "SERVIDOR", "ESTADO 6", rtdo);
			registrarRtdo(rtdo);

			hayRtdos=true;

			//despierto a admin para que pida los rtdos
			synchronized (thread_admin) {
				thread_admin.notify();
			}

		}
	}

	private void estado7(){
		// admin decide si pasa a nuevo tema o cierra sesión.
	}

	private void estado8(){
		try {
			Thread.sleep(10000); // ### usar constante para este timeout
		} catch (InterruptedException e) {
			System.err.println(e.getMessage());
		}
		log.writeLog(Log.INFO, "SERVIDOR", "Estado 8", "Cerrando sesión");
		fin_de_session=true;

		try {
			conectorbd.cerrarConsulta();
		} catch (SQLException e) {
			System.err.println(e.getMessage());

		}
	}

	/**
	 * Método setter para establecer el estado del servidor (usar con cuidado)
	 * @param i : entero con el nro de estado
	 */
	public void set_estado(int i){
		estado_actual=i;
	}

	/**
	 * Método getter que devuelve si hubo fin de sesi�n o no
	 * @return true si finaliz� la sesi�n, caso contrario false
	 */
	public boolean get_fin_sesion(){
		return fin_de_session;
	}

	/**
	 * Método getter que devuelve el estado actual en el que se encuentra el servidor
	 * @return entero con el estado actual
	 */		   
	public int get_estado(){
		return estado_actual;
	}

	/**
	 * Método que realiza la verificaci�n del quorum.
	 * @return true si hay quorum, false caso contrario
	 */
	protected boolean hayQuorum(){
		// acá se verifica que la cantidad de clientes conectados sea mayor a la mitad +1
		// por ahora solo verificaremos que haya 1
		int i=0;
		for(ThreadServerAndroid thread: mapeoClientesAndroid.values()){
			if(thread.clienteEstaConectado())
				i++;
		}
		return i >= quorum;
	}

	/**
	 * Método que devuelve los resultados de la votación en un string
	 * @return String con los resultados de la votación
	 */
	protected String obtenerRtdos(){
		int[] rtdos = procesarRtdos();
		String rtdo = "Positivos: "+rtdos[POSITIVOS]+", Negativos: "+rtdos[NEGATIVOS]+", Abstenciones: "+rtdos[ABSTENCION]+".";
		return rtdo;
	}

	/**
	 * Método que establece el tema actual tratado en la sesión y lo almacena en la BD, también se mantiene en una
	 * variable de la clase.
	 * @param t : String con el tema actual
	 */
	protected void definirTemaActual(String t){
		temaActual = t;
		String sentenciaSQL;
		try {
			//conectorbd.consulta(sentenciaSQL);
			if(conectorbd.insertarTema(t)){

				sentenciaSQL = "SELECT LAST_INSERT_ID();";
				ResultSet rs = conectorbd.consulta(sentenciaSQL);
				rs.next();
				idTemaActual = rs.getInt("LAST_INSERT_ID()");

				sentenciaSQL = "INSERT INTO temas_por_sesion (id_sesion, id_tema) VALUES ("+idSesionActual+", "+idTemaActual+");";
				conectorbd.consulta(sentenciaSQL);

				log.writeLog(Log.INFO, "SERVIDOR", "Registro Tema", "El tema "+t+" se registro correctamente");
			}else{
				log.writeLog(Log.WARNING, "SERVIDOR", "Registro Tema", "No se pudo registrar el tema "+t);
			}

		} catch (SQLException e) {
			log.writeLog(Log.WARNING, "SERVIDOR", "Registro Tema", "No se pudo registrar el tema "+t+" :"+e.getMessage());
		}
	}

	/**
	 * Getter que devuelve el tema actual que se est� tratando.
	 * @return string con el tema actual.
	 */
	protected String obtenerTemaActual(){
		return temaActual;
	}

	/**
	 * Método sincronizado (para usar por threads) que recibe el voto de un cliente. Hace verificaciones (¿Cuales?) y lo registra en la bd.
	 * @param voto : String con el voto del cliente.
	 * @param fingerprint_cliente : String con el fingerprint del cliente (utilizado a modo de ID)
	 * @return true si el voto fue registrado correctamente, false en caso contrario 
	 */
	protected synchronized boolean enviarVoto(String voto, String fingerprint_cliente){

		String msj = "VOTO RECIBIDO: Asambleista "+fingerprint_cliente+" emitio voto "+voto;
		log.writeLog(Log.INFO, "SERVIDOR", "Recepción de voto", msj);

		boolean se_registro_voto=false;

		try {
			String sentenciaSQL = "SELECT id_cliente FROM votos_por_tema WHERE id_cliente = '"+fingerprint_cliente+"' AND id_sesion="+idSesionActual+" AND id_tema="+idTemaActual+";";
			ResultSet rs = conectorbd.consulta(sentenciaSQL);
			if(!rs.next()){ // si no hay un voto registrado para este votante
				sentenciaSQL = "INSERT INTO votos_por_tema (id_sesion, id_tema, id_cliente, voto) VALUES ("+idSesionActual+","+idTemaActual+",'"+fingerprint_cliente+"','"+voto+"');";
				int cantModificaciones = conectorbd.modificarBD(sentenciaSQL); // inserto el voto

				se_registro_voto= cantModificaciones > 0;

			}else{
				msj = "El votante con id "+fingerprint_cliente+" ya hab�a emitido su voto para el tema '"+temaActual+"' de la sesion "+idSesionActual;
				log.writeLog(Log.WARNING, "SERVIDOR", "Registro de Voto", msj);
			}

		} catch (SQLException e) {
			System.err.println(e.getMessage());
		}
		return se_registro_voto;
	}

	/**
	 * Getter de la id de la sesi�n actual
	 * @return entero con la id de la sesi�n actual
	 */
	protected int getIdSesionActual(){
		return idSesionActual;
	}

	/**
	 * Getter de la id del tema actual
	 * @return entero con la id de la sesi�n actual
	 */	
	protected int getIdTemaActual(){
		return idTemaActual;
	}

	/**
	 * Setter de la id de la sesi�n actual. Lo registra en la bd y luego actualiza el atributo correspondiente dentro de esta clase.
	 */	
	protected void setSesionActual(){
		String sentenciaSQL = "INSERT INTO sesiones (fecha) VALUES(now());";
		try {
			conectorbd.consulta(sentenciaSQL);

			sentenciaSQL = "SELECT LAST_INSERT_ID();";
			ResultSet rs = conectorbd.consulta(sentenciaSQL);
			rs.next();
			idSesionActual = rs.getInt("LAST_INSERT_ID()");

		} catch (SQLException e) {
			System.err.println(e.getMessage());
		}
	}

	/**
	 * Método que registra un cliente dentro de la sesión actual.
	 * @param id_cliente : string con el fingerprint del cliente
	 */
	protected void registrarClienteConectado(String id_cliente){
		// verificar si anteriormente no fue registrado????
		String sentenciaSQL = "INSERT INTO clientes_por_sesion (id_sesion, id_cliente) VALUES("+idSesionActual+", '"+id_cliente+"');";
		// verificar al admitir si un cliente figura en la tabla clientes???
		try {
			conectorbd.consulta(sentenciaSQL);
			if(conectorbd.cantModificaciones()>0){
				log.writeLog(Log.INFO, "SERVIDOR", "Registro cliente", "Se inserto el cliente "+id_cliente);
			}else{
				log.writeLog(Log.WARNING, "SERVIDOR", "Registro cliente", "No se inserto el cliente "+id_cliente);
			}
		} catch (SQLException e) {
			System.err.println(e.getMessage());
		}
	}

	/**
	 * Método que registra el rtdo de una votación en la bd.
	 * @param rtdo: String con el rtdo de una votación
	 */
	protected void registrarRtdo(String rtdo){
		String sentenciaSQL = "INSERT INTO resultados (id_sesion, id_tema, resultado) VALUES ("+idSesionActual+", "+idTemaActual+", '"+rtdo+"')";
		try {
			conectorbd.consulta(sentenciaSQL);
		} catch (SQLException e) {
			System.err.println(e.getMessage());
		}
	}

	/**
	 * Método que cuenta los votos de una votación y los devuelve en un arreglo
	 * @return arreglo de enteros con los resultados
	 */
	private int[] procesarRtdos(){
		int[] resultados = new int[3];
		String sentenciaSQL;
		ResultSet rs;
		try{

			sentenciaSQL="SELECT COUNT(*) FROM votos_por_tema WHERE id_sesion="+idSesionActual+" AND id_tema="+idTemaActual+" AND voto='"+Mensaje.VOTO_POSITIVO+"';";
			rs = conectorbd.consulta(sentenciaSQL);
			rs.next();
			resultados[POSITIVOS] = rs.getInt("COUNT(*)");

			sentenciaSQL="SELECT COUNT(*) FROM votos_por_tema WHERE id_sesion="+idSesionActual+" AND id_tema="+idTemaActual+" AND voto='"+Mensaje.VOTO_NEGATIVO+"';";
			rs = conectorbd.consulta(sentenciaSQL);
			rs.next();
			resultados[NEGATIVOS] = rs.getInt("COUNT(*)");

			sentenciaSQL="SELECT COUNT(*) FROM votos_por_tema WHERE id_sesion="+idSesionActual+" AND id_tema="+idTemaActual+" AND voto='"+Mensaje.VOTO_ABSTENCION+"';";
			rs = conectorbd.consulta(sentenciaSQL);
			rs.next();
			resultados[ABSTENCION] = rs.getInt("COUNT(*)");

		}catch(SQLException e){
			System.err.println(e.getMessage());
		};
		return resultados;
	}

	/**
	 * Método que determina si un dado cliente se encuentra habilitado para votar. 
	 * Se realizan 2 verificaciones:
	 * 	- Que en la bd figure como habilitado.
	 * 	- Que no haya otro cliente conectado a la vez con el mismo fingerprint.
	 * 	
	 * @param fingerprint
	 * @return true si está habilitado, false caso contrario.
	 */
	private boolean cliente_habilitado_a_conectarse(String fingerprint){
		boolean esta_habilitado= mapeoClientesAndroid.get(fingerprint)==null;

		try {
			String sentenciaSQL="SELECT habilitado FROM clientes WHERE clave_publica = '"+fingerprint+"';";
			ResultSet rs = conectorbd.consulta(sentenciaSQL);
			if(rs.next()){
				String habilitado = rs.getString("habilitado");
				esta_habilitado = esta_habilitado && habilitado.equals("si");
			}else{ // no figura un cliente en la bd con el fingerprint recibido
				return false;
			}

		} catch (SQLException e) {
			System.err.println(e.getMessage());
		}

		return esta_habilitado;
	}

	/**
	 * Método que realiza la verificación de si todos los clientes, aún conectados, votaron.
	 * @return true si todos votaron, caso contrario false.
	 */
	private boolean verificarTodosVotaron1() {
		boolean todos_votaron=true;
		for(ThreadServerAndroid thread: mapeoClientesAndroid.values()){
			if(thread.clienteEstaConectado()){ // <<<<<<<<<<<<<<<<<<<<< ver si esto realmente funciona bien o si vemos si isAlive()
				try {
					String sentenciaSQL="SELECT * FROM votos_por_tema WHERE id_sesion = "+idSesionActual+" AND id_tema = "+idTemaActual+" AND id_cliente = '"+thread.obtenerFingerprint()+"';";
					ResultSet rs = conectorbd.consulta(sentenciaSQL);
					if(!rs.next())
						return false; // uno no vot�...

				} catch (SQLException e) {
					System.err.println(e.getMessage());
				}
			}
		}
		return todos_votaron;
	}

	/**
	 * Método que verifica si todos los clientes conectados fueron notificados(utilizado en estado 3). [ver que es lo que se les notifica]
	 * @return true si todos fueron notificados, caso contrario false
	 */
	private boolean verificarTodosNotificados(){
		for(ThreadServerAndroid cliente: mapeoClientesAndroid.values()){
			Mensaje[] ultimoMsj = cliente.obtenerUltimoIntercambio();
			String tipoRta = ultimoMsj[ThreadServerAndroid.RESPUESTA].getTipo_msj();
			String tipoPregunta = ultimoMsj[ThreadServerAndroid.PREGUNTA].getTipo_msj(); 
			boolean no_fue_notificado = cliente.clienteEstaConectado() && (tipoPregunta.equals(Mensaje.TIPO_HAY_NUEVO_TEMA) && tipoRta.equals(Mensaje.TIPO_NO));
			if(no_fue_notificado)
				return false;
		}
		return true;
	}

	/**
	 * Método que verifica si todos los clientes conectados fueron notificados(utilizado en estado 4). [ver que es lo que se les notifica]
	 * @return true si todos fueron notificados, caso contrario false
	 */	
	private boolean verificarTodosNotificadosE4(){
		for(ThreadServerAndroid cliente: mapeoClientesAndroid.values()){// debería verificara solamente aquellos AUN CONECTADOS!!!!!!!!!!!!!!
			boolean fue_notificado =cliente.notificadoPorHayTema();
			if(cliente.clienteEstaConectado() && !fue_notificado) // <<<<<<<<<<<<<<< era tan sencillo??????
				return false;
		}
		return true;
	}
	protected int cantClientesConectados(){
		int cant=0;
		synchronized (listaClientesAndroid) {
			for(ThreadServerAndroid cliente: listaClientesAndroid){// deber�a verificara solamente aquellos AUN CONECTADOS!!!!!!!!!!!!!!
				if(cliente.clienteEstaConectado()) // <<<<<<<<<<<<<<< era tan sencillo??????
					cant++;
			}
		}

		return cant;
	}
	protected String nbresClientesConectados(){
		StringBuffer sb=new StringBuffer();
		synchronized (listaClientesAndroid) {
			for(ThreadServerAndroid cliente: listaClientesAndroid){// deber�a verificara solamente aquellos AUN CONECTADOS!!!!!!!!!!!!!!
				if(cliente.clienteEstaConectado()){ 
					if((estado_actual==5 || estado_actual==6 || estado_actual==7) && cliente.emitioVoto())
						sb.append("[VOTO]"+cliente.getNombre()+"-");
					else
						sb.append(cliente.getNombre()+"-");
				}else{
					sb.append("[DESCONECTADO]"+cliente.getNombre()+"-");
				}
			}
		}
		if(sb.length()>1){
			sb.deleteCharAt(sb.length()-1);// para sacar el último guión '-'
		}
		return sb.toString();
	}


	protected synchronized boolean verificarIMEI(String fingerprint, String imei_socket){

		try{
			String hash_imei_socket=finger.cacularHash(imei_socket);
			String sentenciaSQL="SELECT imei FROM clientes WHERE clave_publica = '"+fingerprint+"';";
			ResultSet rs = conectorbd.consulta(sentenciaSQL);
			if(rs.next()){// no debería fallar porque ya se verifico previamente que existia
				String hash_imei_bd = rs.getString("imei");
				if(hash_imei_bd.equals("VACIO")){ // el imei aún no fue registrado, así que se inserta el recibido en la bd
					sentenciaSQL = "UPDATE clientes SET imei='"+hash_imei_socket+"' WHERE clave_publica='"+fingerprint+"';";
					conectorbd.consulta(sentenciaSQL);
					log.writeLog(Log.INFO, "SERVIDOR", "Verificacion IMEI", "Se inserto el IMEI "+hash_imei_socket+" para el cliente "+fingerprint);	
					return true;
				}else{
					return hash_imei_bd.equals(hash_imei_socket);
				}

			}else{ // no figura un cliente en la bd con el fingerprint recibido
				return false;
			}
		}catch(SQLException e){
			System.err.println(e.getMessage());
			return false;
		}

	}
	protected void removerClienteImeiInvalido(String fingerprint, ThreadServerAndroid thread){
		synchronized (mapeoClientesAndroid) {
			mapeoClientesAndroid.remove(fingerprint);
		}
		synchronized (listaClientesAndroid) {
			listaClientesAndroid.remove(thread);
		}

	}
}